/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Operations.Spatial;

import Global.Global;
import Operations.*;
import java.awt.image.BufferedImage;

/**
 *
 * @author littlechild
 */
public class OpCanny implements Operation {
    static int rgbmask=0xffffff;
    double direction[][];
    double strength[][];
    BufferedImage lastImage=null;
    boolean debug=false;
    public static int modeNeighbour=1;
    public static int modeTracingEdge=2;
    public BufferedImage operate(BufferedImage image, double[] paras) {
        int Threshold_H=(int)paras[0];
        int Threshold_L=(int)paras[1];
        int mode=(int)paras[2];
        double angleRegion=paras[3];
        if(lastImage==null || !lastImage.equals(image)) {
            OpNeighbour opneightbour=new OpNeighbour();
            BufferedImage blurredImage;
            //smooth the image
            blurredImage=opneightbour.operate(image, new double []{Global.opGaussFiltering});
            //compute the local gradient (direction and strength)
            //direction ranging from -pi to pi
            direction=new double [image.getWidth()][image.getHeight()];
            strength=new double [image.getWidth()][image.getHeight()];
            computeLocalGradient(blurredImage,direction,strength);
            if(debug) {
                for(int i=0;i!=image.getWidth();++i)  {
                    for (int j=0;j!=image.getHeight();++j) {
                        int gray=(int)strength[i][j];
                        gray=gray> 0xff ? 0xff : gray;
                        System.out.println("i:"+i+";j:"+j+";"+strength[i][j]);
                        blurredImage.setRGB(i, j, gray|(gray<<8)|(gray<<16));
                    }
                }
                return blurredImage;
            }
           
            //non-maximum supression using double linear interpolate
            for(int i=0;i!=image.getWidth();++i)  {
                for (int j=0;j!=image.getHeight();++j) {
                    double point1=Operations.Geometry.Utilities.doubleLinearInterpolate(
                            strength, i+Math.cos(direction[i][j]), j+Math.sin(direction[i][j]));
                    double point2=Operations.Geometry.Utilities.doubleLinearInterpolate(
                            strength, i+Math.cos(-direction[i][j]), j+Math.sin(-direction[i][j]));
                    if(strength[i][j] < point1 || strength[i][j] < point2) {
                        strength[i][j]=0;
                    }
                }
            }
        }
        //streaking elimination
        BufferedImage returnImage=new BufferedImage(image.getWidth(),image.getHeight(),image.getType());
        //two possible versions, tracing the edge or focus on the neighbourhood
        boolean isSolid[][]=null;
        if(mode==modeTracingEdge) {
            isSolid=new boolean [returnImage.getWidth()][returnImage.getHeight()];
            for(int i=0;i!=isSolid.length;++i) {
                for(int j=0;j!=isSolid[0].length;++j) {
                    isSolid[i][j]=false;
                }
            }
        }
        boolean isChanged=true;
        while(isChanged) {
            isChanged=false;
            for(int i=0;i!=returnImage.getWidth();++i) {
                for(int j=0;j!=returnImage.getHeight();++j) {
                    if(strength[i][j]>Threshold_H || (mode==modeTracingEdge && isSolid[i][j]) ){
                        returnImage.setRGB(i, j, rgbmask);
                    }else if(strength[i][j]>Threshold_L) {
                        for(int m=i-1;m!=i+1;++m) {
                            for(int n=j-1;n!=j+1;++n) {
                                if(m>=0 && m<strength.length 
                                        && n>=0 && n< strength[0].length 
                                        && (strength[m][n]>Threshold_H || (mode==modeTracingEdge && isSolid[m][n])) ){
                                    //the following computation can be optimized
                                    double direct=getAngle(i-m, j-n);
                                    double direct1,direct2;
                                    //notice direction[m][n] is the direction of the normal vector
                                    if(direction[m][n]>Math.PI/2) {
                                        direct1=direction[m][n]-Math.PI/2;
                                        direct2=direction[m][n]+Math.PI/2-2*Math.PI;
                                    }else if (direction[m][n]<-Math.PI/2) {
                                        direct1=direction[m][n]-Math.PI/2+2*Math.PI;
                                        direct2=direction[m][n]+Math.PI/2;
                                    }else {
                                        direct1=direction[m][n]+Math.PI/2;
                                        direct2=direction[m][n]-Math.PI/2;
                                    }
                                    //now we will check whether direct is near direct1 or direct2
                                    if(Math.abs(direct-direct1)<=angleRegion 
                                            || Math.abs(direct-direct2)<=angleRegion) {
                                        returnImage.setRGB(i, j, rgbmask);
                                        if(mode==OpCanny.modeTracingEdge) {
                                            isChanged=true;
                                            isSolid[i][j]=true;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        lastImage=image;
        return returnImage;
    }

    private void computeLocalGradient(BufferedImage image, double[][] direction,double [][]strength) {
        //set up template
        int[][] sobelx=new int[][]{
          new int[]{-1, 0 ,1},
          new int[]{-2, 0 ,2},
          new int[]{-1, 0 ,1}
        };
        int [][]sobely=new int[][]{
          new int[]{-1, -2 ,-1},
          new int[]{0, 0 ,0},
          new int[]{1, 2 ,1}
        };
        IntTemplate templatex=new IntTemplate();
        templatex.template=sobelx;
        templatex.factor=1/4.0;
        IntTemplate templatey=new IntTemplate();
        templatey.template=sobely;
        templatey.factor=1/4.0;
        // start to do the iterator
        IntTemplate tmpltx=(IntTemplate)templatex;
        int templatexWidth=tmpltx.template[0].length;
        int templatexHeight=tmpltx.template.length;
        int templatexMiddlex=(templatexWidth-1)/2;
        int templatexMiddley=(templatexHeight-1)/2;
        IntTemplate tmplty=(IntTemplate)templatey;
        int templateyWidth=tmplty.template[0].length;
        int templateyHeight=tmplty.template.length;
        int templateyMiddlex=(templateyWidth-1)/2;
        int templateyMiddley=(templateyHeight-1)/2;
        for (int i=0;i!=image.getWidth();++i) {
            for (int j=0;j!=image.getHeight();++j) {
                int gray1=0;
                int indexx;
                int indexy;
                for(int n=0;n!=templatexWidth;++n) {
                    for (int m=0;m!=templatexHeight;++m) {
                        indexx=i+n-templatexMiddlex;
                        indexy=j+m-templatexMiddley;
                        gray1+=tmpltx.template[m][n]
                                *OpNeighbour.getGray(Operations.Geometry.Utilities.getFrom(image,indexx, indexy));
                    }
                }
                int gray2=0;
                for (int m=0;m!=templateyHeight;++m) {
                     for(int n=0;n!=templateyWidth;++n) {
                        indexx=i+n-templateyMiddlex;
                        indexy=j+m-templateyMiddley;
                        gray2+=tmplty.template[m][n]
                                *OpNeighbour.getGray(Operations.Geometry.Utilities.getFrom(image,indexx, indexy));
                    }
                }
                strength[i][j]=Math.abs(gray1*tmpltx.factor)+Math.abs(gray2*tmplty.factor);
                //from -pi to pi
                direction[i][j]=Math.atan2(gray2, gray1);
               /* if(strength[i][j]!=0) {
                    int ddf=0;
                    ddf++;
                }*/
            }
        }
    }

    private double getAngle(int x, int y) {
        if(x==0) {
            if(y==1)
                return Math.PI/2;
            else
                return -Math.PI/2;
        }else if(x==1) {
            if(y==0)
                return 0;
            else if(y==1)
                return Math.PI/4;
            else
                return -Math.PI/4;
        }else { //x==-1
            if(y==0)
                return Math.PI;
            else if(y==1)
                return 3*Math.PI/4;
            else
                return -3*Math.PI/4;
        }
    }
}
